import { IsString, IsNumber, IsOptional, IsArray, IsBoolean, IsEnum, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export enum OpenAIRole {
  SYSTEM = 'system',
  USER = 'user',
  ASSISTANT = 'assistant',
  FUNCTION = 'function',
  TOOL = 'tool'
}

export enum OpenAIFinishReason {
  STOP = 'stop',
  LENGTH = 'length',
  FUNCTION_CALL = 'function_call',
  TOOL_CALLS = 'tool_calls',
  CONTENT_FILTER = 'content_filter'
}

export class OpenAIMessage {
  @ApiProperty({ enum: OpenAIRole, description: 'The role of the message author' })
  @IsEnum(OpenAIRole)
  role: OpenAIRole;

  @ApiProperty({ description: 'The content of the message' })
  @IsString()
  content: string;

  @ApiPropertyOptional({ description: 'The name of the author of this message' })
  @IsString()
  @IsOptional()
  name?: string;

  @ApiPropertyOptional({ description: 'The function call that the model wants to make' })
  @IsOptional()
  function_call?: {
    name: string;
    arguments: string;
  };

  @ApiPropertyOptional({ description: 'The tool calls generated by the model' })
  @IsOptional()
  tool_calls?: Array<{
    id: string;
    type: 'function';
    function: {
      name: string;
      arguments: string;
    };
  }>;

  @ApiPropertyOptional({ description: 'Tool call ID that this message is responding to' })
  @IsString()
  @IsOptional()
  tool_call_id?: string;
}

export class OpenAIFunction {
  @ApiProperty({ description: 'The name of the function to be called' })
  @IsString()
  name: string;

  @ApiPropertyOptional({ description: 'A description of what the function does' })
  @IsString()
  @IsOptional()
  description?: string;

  @ApiProperty({ description: 'The parameters the functions accepts, described as a JSON Schema object' })
  parameters: any;
}

export class OpenAITool {
  @ApiProperty({ enum: ['function'], description: 'The type of the tool' })
  @IsString()
  type: 'function';

  @ApiProperty({ type: OpenAIFunction, description: 'The function definition' })
  @ValidateNested()
  @Type(() => OpenAIFunction)
  function: OpenAIFunction;
}

export class OpenAIChatCompletionRequest {
  @ApiProperty({ description: 'ID of the model to use' })
  @IsString()
  model: string;

  @ApiProperty({ type: [OpenAIMessage], description: 'A list of messages comprising the conversation so far' })
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => OpenAIMessage)
  messages: OpenAIMessage[];

  @ApiPropertyOptional({ description: 'What sampling temperature to use, between 0 and 2', minimum: 0, maximum: 2 })
  @IsNumber()
  @IsOptional()
  temperature?: number;

  @ApiPropertyOptional({ description: 'An alternative to sampling with temperature', minimum: 0, maximum: 1 })
  @IsNumber()
  @IsOptional()
  top_p?: number;

  @ApiPropertyOptional({ description: 'How many chat completion choices to generate for each input message', minimum: 1 })
  @IsNumber()
  @IsOptional()
  n?: number;

  @ApiPropertyOptional({ description: 'If set, partial message deltas will be sent as data-only server-sent events' })
  @IsBoolean()
  @IsOptional()
  stream?: boolean;

  @ApiPropertyOptional({ description: 'Up to 4 sequences where the API will stop generating further tokens' })
  @IsOptional()
  stop?: string | string[];

  @ApiPropertyOptional({ description: 'The maximum number of tokens to generate in the chat completion' })
  @IsNumber()
  @IsOptional()
  max_tokens?: number;

  @ApiPropertyOptional({ description: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far' })
  @IsNumber()
  @IsOptional()
  presence_penalty?: number;

  @ApiPropertyOptional({ description: 'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far' })
  @IsNumber()
  @IsOptional()
  frequency_penalty?: number;

  @ApiPropertyOptional({ description: 'Modify the likelihood of specified tokens appearing in the completion' })
  @IsOptional()
  logit_bias?: Record<string, number>;

  @ApiPropertyOptional({ description: 'A unique identifier representing your end-user' })
  @IsString()
  @IsOptional()
  user?: string;

  @ApiPropertyOptional({ type: [OpenAITool], description: 'A list of tools the model may call' })
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => OpenAITool)
  @IsOptional()
  tools?: OpenAITool[];

  @ApiPropertyOptional({ description: 'Controls which (if any) function is called by the model' })
  @IsOptional()
  tool_choice?: 'none' | 'auto' | { type: 'function'; function: { name: string } };

  @ApiPropertyOptional({ description: 'Deprecated. Use tools instead.' })
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => OpenAIFunction)
  @IsOptional()
  functions?: OpenAIFunction[];

  @ApiPropertyOptional({ description: 'Deprecated. Use tool_choice instead.' })
  @IsOptional()
  function_call?: 'none' | 'auto' | { name: string };
}

export class OpenAIChoice {
  @ApiProperty({ description: 'The index of the choice in the list of choices' })
  index: number;

  @ApiProperty({ type: OpenAIMessage, description: 'A chat completion message generated by the model' })
  message: OpenAIMessage;

  @ApiProperty({ enum: OpenAIFinishReason, description: 'The reason the model stopped generating tokens' })
  finish_reason: OpenAIFinishReason;
}

export class OpenAIUsage {
  @ApiProperty({ description: 'Number of tokens in the prompt' })
  prompt_tokens: number;

  @ApiProperty({ description: 'Number of tokens in the generated completion' })
  completion_tokens: number;

  @ApiProperty({ description: 'Total number of tokens used in the request (prompt + completion)' })
  total_tokens: number;
}

export class OpenAIChatCompletionResponse {
  @ApiProperty({ description: 'A unique identifier for the chat completion' })
  id: string;

  @ApiProperty({ description: 'The object type, which is always "chat.completion"' })
  object: string;

  @ApiProperty({ description: 'The Unix timestamp (in seconds) of when the chat completion was created' })
  created: number;

  @ApiProperty({ description: 'The model used for the chat completion' })
  model: string;

  @ApiProperty({ type: [OpenAIChoice], description: 'A list of chat completion choices' })
  choices: OpenAIChoice[];

  @ApiProperty({ type: OpenAIUsage, description: 'Usage statistics for the completion request' })
  usage: OpenAIUsage;

  @ApiPropertyOptional({ description: 'This fingerprint represents the backend configuration that the model runs with' })
  system_fingerprint?: string;
}

export class OpenAIModel {
  @ApiProperty({ description: 'The model identifier' })
  id: string;

  @ApiProperty({ description: 'The object type, which is always "model"' })
  object: string;

  @ApiProperty({ description: 'The Unix timestamp (in seconds) of when the model was created' })
  created: number;

  @ApiProperty({ description: 'The organization that owns the model' })
  owned_by: string;
}

export class OpenAIModelsResponse {
  @ApiProperty({ description: 'The object type, which is always "list"' })
  object: string;

  @ApiProperty({ type: [OpenAIModel], description: 'The list of models' })
  data: OpenAIModel[];
}

export class OpenAIEmbeddingRequest {
  @ApiProperty({ description: 'ID of the model to use' })
  @IsString()
  model: string;

  @ApiProperty({ description: 'Input text to embed, encoded as a string or array of tokens' })
  input: string | string[] | number[] | number[][];

  @ApiPropertyOptional({ description: 'The format to return the embeddings in' })
  @IsString()
  @IsOptional()
  encoding_format?: 'float' | 'base64';

  @ApiPropertyOptional({ description: 'The number of dimensions the resulting output embeddings should have' })
  @IsNumber()
  @IsOptional()
  dimensions?: number;

  @ApiPropertyOptional({ description: 'A unique identifier representing your end-user' })
  @IsString()
  @IsOptional()
  user?: string;
}

export class OpenAIEmbedding {
  @ApiProperty({ description: 'The object type, which is always "embedding"' })
  object: string;

  @ApiProperty({ description: 'The embedding vector' })
  embedding: number[];

  @ApiProperty({ description: 'The index of the embedding in the list of embeddings' })
  index: number;
}

export class OpenAIEmbeddingResponse {
  @ApiProperty({ description: 'The object type, which is always "list"' })
  object: string;

  @ApiProperty({ type: [OpenAIEmbedding], description: 'The list of embeddings generated by the model' })
  data: OpenAIEmbedding[];

  @ApiProperty({ description: 'The name of the model used to generate the embedding' })
  model: string;

  @ApiProperty({ type: OpenAIUsage, description: 'The usage information for the request' })
  usage: OpenAIUsage;
}